// WebSocket singleton service for the app
// Server: ws://localhost:5090/ws
const proto = location.protocol === 'https:' ? 'wss' : 'ws'
const host = '127.0.0.1'
const DEFAULT_WS_URL = `${proto}://${host}:5090/ws`
// const DEFAULT_WS_URL = 'ws://100.65.240.60:5090/ws'

/**
 * Module-scoped singleton state
 */
let socketInstance = null
let connectionPromise = null

// Map key: `${genre}:${type}` -> callback(payload, rawMessage)
const messageHandlers = new Map()

/**
 * Build a handler key from incoming message fields
 * @param {string} genre
 * @param {string} type
 */
function buildKey(genre, type) {
  return `${String(genre)}:${String(type)}`
}

/**
 * Initialize (or reuse) the WebSocket connection.
 * Returns a promise that resolves when the socket is OPEN.
 * @param {string} [url]
 * @returns {Promise<WebSocket>}
 */
function init(url = DEFAULT_WS_URL) {
  if (socketInstance && socketInstance.readyState === WebSocket.OPEN) {
    return Promise.resolve(socketInstance)
  }
  if (connectionPromise) {
    return connectionPromise
  }

  connectionPromise = new Promise((resolve, reject) => {
    try {
      // Close previous socket if half-open or closing
      if (socketInstance && socketInstance.readyState !== WebSocket.CLOSED) {
        try { socketInstance.close() } catch (_) {}
      }

      socketInstance = new WebSocket(url)

      socketInstance.onopen = () => {
        const ws = socketInstance
        connectionPromise = null
        resolve(ws)
      }

      socketInstance.onerror = (event) => {
        // Surface error to awaiting callers
        const err = new Error('WebSocket error')
        err.event = event
        // Reset promise so next init can retry
        connectionPromise = null
        reject(err)
      }

      socketInstance.onmessage = (event) => {
        const raw = event && event.data
        let parsed
        try {
          parsed = typeof raw === 'string' ? JSON.parse(raw) : JSON.parse(String(raw))
        } catch (_) {
          return // Ignore non-JSON payloads
        }

        const genre = parsed && parsed.genre
        const type = parsed && parsed.type
        const key = buildKey(genre, type)
        const handler = messageHandlers.get(key)
        if (typeof handler === 'function') {
          try {
            handler(parsed, raw)
          } catch (_) {
            // Swallow handler errors to avoid breaking message loop
          }
        }
      }

      socketInstance.onclose = () => {
        // Reset state so future init() calls can reconnect
        connectionPromise = null
      }
    } catch (e) {
      connectionPromise = null
      reject(e)
    }
  })

  return connectionPromise
}

/**
 * Send a message to the server. If given an object, it is JSON.stringified.
 * Ensures the socket is open before sending.
 * @param {string|object} data
 * @returns {Promise<void>}
 */
async function send(data) {
  const ws = await init()
  const payload = typeof data === 'string' ? data : JSON.stringify(data)
  ws.send(payload)
}

/**
 * Register a callback for messages matching genre and type.
 * @param {string} genre
 * @param {string} type
 * @param {(payload: any, raw: string) => void} callback
 */
function on(genre, type, callback) {
  const key = buildKey(genre, type)
  messageHandlers.set(key, callback)
}

/**
 * Remove a previously registered callback.
 * @param {string} genre
 * @param {string} type
 */
function off(genre, type) {
  const key = buildKey(genre, type)
  messageHandlers.delete(key)
}

/**
 * Close the socket (if any) and reset the singleton state.
 */
function close() {
  if (socketInstance) {
    try { socketInstance.close() } catch (_) {}
  }
  socketInstance = null
  connectionPromise = null
}

export default {
  init,
  send,
  on,
  off,
  close,
}


